The pIMPL Idiom in C++ (ENG)

Separating Interface and Implementation

In many code design patterns, people make all the declarations in the hpp file and definitions in the cpp file. This is called separating the interface and the implementation. Here’s an example:

// myLib.hpp
#ifndef MYLIB_HPP
#define MYLIB_HPP

class myLib {
private:
    int num;
public:
    myLib(int n); // Constructor taking an int parameter
    ~myLib() = default; // Destructor doing nothing
    int add(int n); // Method taking an int parameter
    int sub(int n); // Method taking an int parameter
};
#endif

// myLib.cpp
#include "myLib.hpp"

myLib::myLib(int n) : num{n} {}
int myLib::add(int n) {
    num += n;
    return num;
}
int myLib::sub(int n) {
    num -= n;
    return num;
}

After compilation, the user will not see any implementation details of the library, that is, our cpp file. This approach has several benefits, such as encapsulation and ease of use. But, sometimes it is better not to expose any interface details about the implementation. In such cases, we can introduce the pimpl idiom.

The pIMPL Idiom at a Peek

We will demonstrate how our last code example above applies the pimpl idiom. With the pimpl idiom, the example above will look like:

// myLib.hpp

#ifndef MYLIB_HPP
#define MYLIB_HPP

class myLibImpl; // Forward declaration of the implementation class

class myLib {
private:
    myLibImpl* pImpl; // Pointer to the implementation class
public:
	void operation(int n);
    myLib(int n); // Constructor taking an int parameter
    ~myLib();
    int add(int n);
    int sub(int n);
// Rule of five...
};
#endif

// myLib.cpp
#include "myLib.hpp"
#include <iostream>

// Implementation of the myLibImpl class
class myLibImpl {
public:
    int num;
    myLibImpl(int n) : num{n} {}

    int add(int n) {
        num += n;
        return num;
    }
    int sub(int n) {
        num -= n;
        return num;
    }
    void operation(int n) {
        std::cout << "Operation with value: " << n << std::endl;
    }
};

// Constructor
myLib::myLib(int n) : pImpl{new myLibImpl(n)} {}
// Destructor
myLib::~myLib() {
    delete pImpl;
}
// Operation method
void myLib::operation(int n) {
    pImpl->operation(n);
}

// Rule of five implementations...

We see, everything about the implementation is in one translation unit, and what we have in myLib.hpp is really an interface to myLibImpl.

You can see the code is now safer to use, but one level of indirection is added, which may impact performance.

There's Further More...

I am not done with the pIMPL design pattern.